package commons.drawing.couche;

import commons.data.I_Searchable;
import commons.drawing.I_Drawable;
import commons.ihm.ParametresAffichageCarto;
import commons.ihm.ParametresAffichageCarto.ModeAffichage;
import java.util.ArrayList;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import gnu.trove.set.hash.THashSet;
import java.awt.Graphics2D;
import java.util.regex.Pattern;

public abstract class A_Couche implements I_Couche {

	protected String name;

	protected ArrayList<I_Drawable> vSelection;
	

	public A_Couche (String name) {
		this.name = name;
		vSelection = new ArrayList<I_Drawable>(20);
	}

	@Override
	public String getName() {return name;}

	@Override
	public String toString() {return getName();}

	@Override
	public void draw (Graphics2D g, ParametresAffichageCarto pac, Rect_d rectVisu) {
		ArrayList<I_Drawable> vToDraw=null;

		if (pac.modeAffichage == ModeAffichage.NORMAL)
			vToDraw = getDrawables(rectVisu);
		else if (pac.modeAffichage == ModeAffichage.SELECTION)
			vToDraw = getSelection();
		else
			// Le mode Highlight ne concerne qu'un objet à la fois, et son affichage est géré par le PanelCarto
			assert false : "Mode d'affichage non pris en compte pour l'affichage d'une couche : " + pac.modeAffichage;

		if (vToDraw==null) return;
		
		for (I_Drawable drawable : vToDraw)
			drawable.draw(g, pac);
	}
	
	@Override
	public void clearSelection() {vSelection.clear();}

	// Renvoit une sélection précalculée au préalable (à l'aide de select())
	@Override
	public ArrayList<I_Drawable> getSelection() {return vSelection;}

	// Créée une sélection basée sur un point et avec une tolérance
	@Override
	public void select (Point_d pt_m, double tolerance_m) {
		vSelection.clear();

		// On lance la recherche, en limitant à un rectangle
		Rect_d rectRecherche_m = new Rect_d(pt_m, tolerance_m*2, tolerance_m*2);
		ArrayList<I_Drawable> vDrawable = getDrawables(rectRecherche_m);

		if (vDrawable==null) return;
		
		for (I_Drawable drawable : vDrawable) {
			if (drawable.distTo(pt_m) <= tolerance_m)
				vSelection.add(drawable);
		}
	}

	@Override
	public void select (String pattern) {
		addToSelection(search(pattern));
	}

	@Override
	public void select (ArrayList<I_Drawable> vObj) {
		addToSelection(search(vObj));
	}

	
	@Override
	public ArrayList<I_Drawable> search (String regex) {
		assert (regex!=null);
		ArrayList<I_Drawable> vAllObjects = getAllObjects();
		if (vAllObjects == null) return null;
		ArrayList<I_Drawable> vResultSearch = new ArrayList<I_Drawable>();

		// couche non searchable
		if (!isSearchable())
			return vResultSearch;
		
		//recherche vide ?
		if (regex.trim().length()==0)
			return vResultSearch;

		Pattern p = Pattern.compile(regex);
		
		for (I_Drawable o : vAllObjects) {
			if (o instanceof I_Searchable)
				if ( ((I_Searchable)o).matchSearch(p) )
					vResultSearch.add(o);
		}
		return vResultSearch;
	}
	
	// Une couche est searchable si son premier objet l'est, sinon tant pis pour elle !
	private boolean isSearchable() {
		ArrayList<I_Drawable> vAllObjects = getAllObjects();
		if (vAllObjects.isEmpty())
			return false;
		return vAllObjects.get(0) instanceof I_Searchable;
	}

	// Recherche les I_Drawable correspondant aux objects fournis en entrée
	//  il se peut que les objets ne fassent pas partie de la couche
	// => ne renvoie que les objets effectivement contenus dans la couche
	@Override
	public ArrayList<I_Drawable> search (ArrayList<I_Drawable> vObjectToSearch) {
		if (vObjectToSearch==null || vObjectToSearch.isEmpty()) return null;

		ArrayList<I_Drawable> vResultSearch = new ArrayList<I_Drawable>();
		ArrayList<I_Drawable> vAllObjects = getAllObjects();

//		HashSet<I_Drawable> setObjectToSearch = new HashSet<I_Drawable>();
//		setObjectToSearch.addAll(vObjectToSearch);
//
//		for (I_Drawable o : vAllObjects) {
//			if (setObjectToSearch.contains(o))
//				vResultSearch.add(o);
//		}
		
		// moins efficace de faire un set sur l'ensemble des objets de la couche plutot que sur les objets a rechercher
		// mais cette methode garanti le maintient de l'ordre dans la sélection des objets recherchés 
		THashSet<I_Drawable> setAllObjects = new THashSet<I_Drawable>();
		setAllObjects.addAll(vAllObjects);

		for (I_Drawable o : vObjectToSearch) {
			if (setAllObjects.contains(o))
				vResultSearch.add(o);
		}

		return vResultSearch;
	}


	// Créée une sélection basée sur une liste d'objet
	//!! Les objets doivent appartenir à la couche
	// cela est garanti si l'argument est issu de search()
	private void addToSelection (ArrayList<I_Drawable> vObjectToSelect) {
		vSelection.clear();
		if (vObjectToSelect==null) return;
		vSelection.addAll (vObjectToSelect);
	}


	// Appelé juste avant l'affichage : retourne les objets à dessiner qui entrent
	//  dans le rectangle de visualisation courant
	// Appelé également pour la fonction de recherche (avec rectVisu==null, dans ce cas)
	// Si le rect est null, doit renvoyer toutes les données
	 abstract protected ArrayList<I_Drawable> getDrawables (Rect_d rectVisu);

	 protected ArrayList<I_Drawable> getAllObjects () {return getDrawables(null);}

}
